/**
 * Camera.cpp
 *
 * Author:
 *   Chris Herbison
 *
 * Description:
 *   Represents the camera for the scene.
 */

//#define DEBUG

#ifdef GDD_LAB_COMPUTER
#include <glut.h>
#else
#include <gl/glut.h>
#endif

#include "Camera.h"
#include "MyColor.h"
#include "IntersectData.h"
#include "Triangle.h"
#include "WorldData.h"
#include "PhongI.h"
#include "RadiositySolution.h"
#include "KDTreeNode.h"

#include <fstream>
#include <iostream>

const double PI = 3.14159265;

/*
 * Paints a pixel at a given point a given color.
 *
 * x - x coordinate of pixel.
 * y - y coordinate of pixel.
 * color - Color to make pixel.
 */
void drawPixel(int x, int y, MyColor color){
     glColor3f(color.r, color.g, color.b);
     glRectd(x, y, x+1, y+1);
}

void Camera::distributeRadiantEnergy(vector<Rectangle*> sceneRectangles, size_t iterations){
	double highestEnergy = 0;
	size_t highestEnergyRectIndex = 0;
	size_t highestEnergyPatchIndex = 0;
	double nextHighestEnergy = 0;
	size_t nextHighestEnergyRectIndex = 0;
	size_t nextHighestEnergyPatchIndex = 0;
	//Find which patch has the highest initial energy
	for(size_t i = 0; i < sceneRectangles.size(); i++){
		double curEnergy = luminance(sceneRectangles.at(i)->patchTree.root.patchInfo.radiantEnergy);
		if(curEnergy > highestEnergy){
			highestEnergyRectIndex = i;
			highestEnergy = curEnergy;
		}
	}

	double curFFTotal = 0;

	for(size_t i = 0; i < iterations; i++){
		curFFTotal = 0;
		nextHighestEnergy = 0;
		nextHighestEnergyRectIndex = 0;
		nextHighestEnergyPatchIndex = 0;
		for(size_t j = 0; j < sceneRectangles.size(); j++){
			if(j != highestEnergyRectIndex){
				for(size_t k = 0; k < sceneRectangles.at(j)->patches.size(); k++){
					//Calculate energy added to patch j from energy exerted by patch i.
					Patch* Pi = sceneRectangles.at(highestEnergyRectIndex)->patches.at(highestEnergyPatchIndex);
					Patch* Pj = sceneRectangles.at(j)->patches.at(k);
					Vector3 iToj = (Pj->midpoint - Pi->midpoint);
					Vector3 jToi = (Pi->midpoint - Pj->midpoint);
					double rSquared = iToj.lengthSquared();
					iToj.normalize();
					jToi.normalize();
					Vector3 iNormal = sceneRectangles.at(highestEnergyRectIndex)->normal;
					Vector3 jNormal = sceneRectangles.at(j)->normal;
					MyColor rohj = sceneRectangles.at(j)->texture.cd;
					MyColor Bi = Pi->radiantEnergy;
					MyColor Bj = Pj->radiantEnergy;
					double FormFactorij = ((iToj*iNormal)*(jToi*jNormal))*(Pj->area)/(PI*rSquared);
					curFFTotal += FormFactorij;

					//Make sure the patches are not blocked
					bool Visibilityij = 0;
					IntersectData dummyData;
					for(size_t intIndex = 0; intIndex < sceneRectangles.size(); intIndex++){
						if(intIndex != j && intIndex != highestEnergyRectIndex && sceneRectangles.at(intIndex)->intersects(Ray(Pi->midpoint, iToj), &dummyData)){
							//Make sure the intersection occured between the two patches
							if(Pi->midpoint.distanceToSquared(dummyData.pos) < Pi->midpoint.distanceToSquared(Pj->midpoint)){
								intIndex = sceneRectangles.size();
							}
							else if(intIndex == (sceneRectangles.size()-1)){
								Visibilityij = 1;
							}
						}
						else if(intIndex == (sceneRectangles.size()-1)){
							Visibilityij = 1;
						}
					}

					Visibilityij = 1;

					MyColor deltaRad = Bi*rohj*(FormFactorij*Visibilityij*(Pi->area)/(Pj->area));
					Bj += deltaRad;
					Pj->radiantEnergy = Bj;

					//Calculate light absorbed for this patch.
					Pj->illuminance += deltaRad;

					//Compare to see if this patch now has the highest energy to exhert
					double curEnergy = luminance(Bj);
					if(curEnergy > nextHighestEnergy){
						nextHighestEnergy = curEnergy;
						nextHighestEnergyRectIndex = j;
						nextHighestEnergyPatchIndex = k;
					}
				}
			}
			//If this is the same surface, loop over other patches just to check if their radiant energy is higher
			else{
				for(size_t k = 0; k < sceneRectangles.at(j)->patches.size(); k++){
					//Don't compare if this is the current max value
					if(k != highestEnergyPatchIndex){
						double curEnergy = luminance(sceneRectangles.at(j)->patches.at(k)->radiantEnergy);
						if(curEnergy > nextHighestEnergy){
							nextHighestEnergy = curEnergy;
							nextHighestEnergyRectIndex = j;
							nextHighestEnergyPatchIndex = k;
						}
					}
				}
			}
		}
		sceneRectangles.at(highestEnergyRectIndex)->patches.at(highestEnergyPatchIndex)->radiantEnergy = MyColor(0,0,0,1);
		highestEnergy = nextHighestEnergy;
		highestEnergyRectIndex = nextHighestEnergyRectIndex;
		highestEnergyPatchIndex = nextHighestEnergyPatchIndex;
		cout << "Loop #" << i << endl;
	}
}

/*
 * Applies Reinhard tone reproduction to a color.
 *
 * iColor - The color to modify.
 */
void Camera::photographicToneReproduction(MyColor *iColor){
     if((*iColor).r < 0){
          (*iColor).r = 0;
     }
     else if((*iColor).r > 1){
          (*iColor).r = 1;
     }
     if((*iColor).g < 0){
          (*iColor).g = 0;
     }
     else if((*iColor).g > 1){
          (*iColor).g = 1;
     }
     if((*iColor).b < 0){
          (*iColor).b = 0;
     }
     else if((*iColor).b > 1){
          (*iColor).b = 1;
     }
     
     float Rxy = (*iColor).r * Lmax;
     float Gxy = (*iColor).g * Lmax;
     float Bxy = (*iColor).b * Lmax;
     
     float Rs = (toneA/Lwa) * Rxy;
     float Gs = (toneA/Lwa) * Gxy;
     float Bs = (toneA/Lwa) * Bxy;
     
     float Rr = Rs/(1+Rs);
     float Gr = Gs/(1+Gs);
     float Br = Bs/(1+Bs);
     
     float Rtar = Rr*Lmax;
     float Gtar = Gr*Lmax;
     float Btar = Br*Lmax;
     
     (*iColor).r = Rtar/Lmax;
     (*iColor).g = Gtar/Lmax;
     (*iColor).b = Btar/Lmax;
}

/*
 * Applies Ward tone reproduction to a color.
 *
 * iColor - The color to modify.
 */
void Camera::perceptualToneReproduction(MyColor *iColor){
     if((*iColor).r < 0){
          (*iColor).r = 0;
     }
     else if((*iColor).r > 1){
          (*iColor).r = 1;
     }
     if((*iColor).g < 0){
          (*iColor).g = 0;
     }
     else if((*iColor).g > 1){
          (*iColor).g = 1;
     }
     if((*iColor).b < 0){
          (*iColor).b = 0;
     }
     else if((*iColor).b > 1){
          (*iColor).b = 1;
     }
     
     (*iColor).r *= scaleFactor;
     (*iColor).g *= scaleFactor;
     (*iColor).b *= scaleFactor;
}

/*
 * Calculates the luminance term of a color.
 *
 * lColor - The color to calculate the luminance of.
 *
 * return (double) - The luminance of the color.
 */
double Camera::luminance(MyColor lColor){
     double Rxy = lColor.r;
     double Gxy = lColor.g;
     double Bxy = lColor.b;
     return 0.27*Rxy+0.67*Gxy+0.06*Bxy;
}

/*
 * Calculates the average illumination of the scene.
 */
void Camera::calculateLwa(){
     double sumOfLogs = 0;
     for(int i = 0; i < (int)screen.size(); i++){
          sumOfLogs += log(EPSILON+luminance(screen.at(i)));
     }
     Lwa = exp(sumOfLogs/(300*300));
}

/*
 * Calculates the world adaption luminance.
 */
void Camera::calculateSf(){
     scaleFactor = pow(((1.219+pow((Lmax/2),0.4)) / (1.219+pow(Lwa,0.4))), 2.5);
}

/*
 * The constructor.
 *
 * _position - The position of the camera in world space.
 * _lookAt - The point that the camera points towards.
 * _up - The orientation of the camera.
 * _Lmax - Scale to multiply by for tone reproduction.
 * toneType - The type of tone reproduction to use {t/f} = {Ward/Reinhard}
 * _Lwa - Pre determined value for Lwa (only to be used in Reinhard tone reproduction).
 * _toneA - Percentage for gray zone (as used in Reinhard tone reproduction).
 */
Camera::Camera(Point3 _position, Vector3 _lookAt, Vector3 _up, double _Lmax, bool toneType, double _Lwa, double _toneA): 
               position(_position), lookAt(_lookAt), up(_up), Lmax(_Lmax), useWard(toneType), Lwa(_Lwa), toneA(_toneA){
}

/*
 * Renders the scene given a world.
 *
 * world - The world to render.
 */
void Camera::render(World w){
    //Load all the lights in to the data object, as they won't be
    //     changing unless the scene needs to be rendered again.
    IntersectData data;
    WorldData staticData;
    //PhongI shader;
	RadiositySolution shader;
    for(int i = 0; i < (int)w.worldLights.size(); i++){
	    staticData.lights.push_back(w.worldLights.at(i));
    }
    for(int i = 0; i < (int)w.worldObjects.size(); i++){
        staticData.objects.push_back(w.worldObjects.at(i));
    }
	
    for(int i = 0; i < (int)w.worldRays.size(); i++){
#ifdef DEBUG
		cout << (((float)i)/((float)w.worldRays.size()))*100 << "% complete" << endl;
#endif
        //Keep track of intersections on this ray for z buffering algorithm later.
        vector<Point3> intersectPoints;
        vector<MyColor> intersectColors;

        //Load in to local variable to avoid too many access calls.
        Ray currentRay(w.worldRays.at(i));
        //Calculate the location of the pixel relative to the ray.
        Point3 pixelPoint(currentRay.direction.x+150, currentRay.direction.y+150, 
                          currentRay.direction.z+150);
        //Pass the viewing vector to the IntersectData class to avoid doing this several times later.
        data.view = currentRay.direction;

		//Get variables for traversing K-D Tree
		//vector<Point3> boxIntersections = w.worldTree.root.AABB.rayIntersections(currentRay);
		vector<Point3> boxIntersections(2);

		if(boxIntersections.size() >= 2){
			//vector<Triangle*> curTriangles = getBoundingBoxes(currentRay, &(w.worldTree.root), boxIntersections.at(0), boxIntersections.at(1));
			vector<Object*> curTriangles = w.worldObjects;

			//Check if this ray hit any objects.
			for(int j = 0; j < (int)curTriangles.size(); j++){
		    //Reset the parity of the object (for transmission rays).
				curTriangles.at(j)->parity = false;
				if(curTriangles.at(j)->intersects(currentRay, &data)){
					//If a ray hits something, shade that pixel given the intersection data, and add it to the vector of intersections.
                    MyColor shadedColor = shader.illuminate(data, curTriangles.at(j), staticData, j, 0);
                    intersectPoints.push_back(data.pos);
                    intersectColors.push_back(shadedColor);
				}
			}
			//Sort all intersections from closest to farthest distance from ray origin.
			// Note: Distance squared is taken for efficiency reasons.
			for(int j = 0; j < (int)intersectPoints.size(); j++){
				int highest = j;
				double highestDist = currentRay.origin.distanceToSquared(intersectPoints.at(highest));
				for(int k = j; k < (int)intersectPoints.size(); k++){
					if(k != highest && currentRay.origin.distanceToSquared(intersectPoints.at(k)) < highestDist){
						highest = k;
						highestDist = currentRay.origin.distanceToSquared(intersectPoints.at(k));
					}
				}
				if(highest != j){
                    Point3 tSwapP(intersectPoints.at(j));
                    MyColor tSwapC(intersectColors.at(j));
                    
                    intersectPoints.at(j) = intersectPoints.at(highest);
                    intersectColors.at(j) = intersectColors.at(highest);
                    intersectPoints.at(highest) = tSwapP;
                    intersectColors.at(highest) = tSwapC;
				}
			}
			//Add points to screen
			if(intersectPoints.size() > 0){
				screen.push_back(intersectColors.at(0));
			}
			else{
				screen.push_back(BG_COLOR);
			}
		 }
		 else{
			 //If the ray is outside of the bounding box, no intersections will occur
			 screen.push_back(BG_COLOR);
		 }
     }

     /*//If Lwa was specified under Ward Tone reproduction, warn user.
     if(Lwa != 0 && useWard){
          cerr << "WARNING: Lwa should not be specified when using Ward tone reproduction! (Ignoring argument)" << endl;
          calculateLwa();
     }
     //If Lwa was not specified, calculate it.
     if(Lwa == 0){
          calculateLwa();
     }
     //Reinhard tone reproduction only.
     if(!useWard){
          for(int i = 0; i < (int)screen.size(); i++){
               photographicToneReproduction(&(screen.at(i)));
          }
     }
     //Ward tone reproduction only.
     else{
          calculateSf();
          for(int i = 0; i < (int)screen.size(); i++){
               perceptualToneReproduction(&(screen.at(i)));
          }
     }*/

     //Draw the screen.
     for(int i = 0; i < (int)screen.size(); i++){
          int x = i/300;
          int y = i%300;
          drawPixel(x, y, screen.at(i));
     }
}
